昨天看過了 call.respondHtml
的實作之後,今天我們來看看
val name = "Ktor"
head {
title {
+name
}
}
裡面的 head
、title
等等函數是怎麼實作的。
我們先來看看 head
@HtmlTagMarker
inline fun HTML.head(crossinline block : HEAD.() -> Unit = {}) : Unit = HEAD(emptyMap, consumer).visit(block)
這邊宣告了 HEAD
類別
@Suppress("unused")
open class HEAD(initialAttributes : Map<String, String>, override val consumer : TagConsumer<*>) : HTMLTag("head", consumer, initialAttributes, null, false, false), HtmlHeadTag
這邊可以看到 HEAD
繼承了 HTMLTag
類別
open class HTMLTag(
override val tagName: String,
override val consumer: TagConsumer<*>,
initialAttributes: Map<String, String>,
override val namespace: String? = null,
override val inlineTag: Boolean,
override val emptyTag: Boolean
) : Tag {
override val attributes: DelegatingMap = DelegatingMap(initialAttributes, this) { consumer }
override val attributesEntries: Collection<Map.Entry<String, String>>
get() = attributes.immutableEntries
}
以及實作了 HtmlHeadTag
介面
interface HtmlHeadTag : CommonAttributeGroupFacade, MetaDataContent {
}
HEAD
裡面覆寫(override)了四個函數,我們一個個看
首先是 Entities.unaryPlus()
override operator fun Entities.unaryPlus() : Unit {
@Suppress("DEPRECATION") entity(this)
}
這邊覆蓋的是 +
(unaryPlus)這個操作子,往下呼叫的是 entity
override fun entity(e : Entities) : Unit {
super<HTMLTag>.entity(e)
}
這個 entity
實作則是
fun entity(e: Entities) {
consumer.onTagContentEntity(e)
}
到這邊,我們終於確定了 Entities.unaryPlus()
實際會做什麼:呼叫 TagConsumer
產生這個標籤裡面的 Content
釐清這段邏輯之後,String.unaryPlus()
看起來也就很簡單了
@Deprecated("This tag most likely doesn't support text content or requires unsafe content (try unsafe {}")
override operator fun String.unaryPlus() : Unit {
@Suppress("DEPRECATION") text(this)
}
@Deprecated("This tag most likely doesn't support text content or requires unsafe content (try unsafe {}")
override fun text(s : String) : Unit {
super<HTMLTag>.text(s)
}
fun text(s: String) {
consumer.onTagContent(s)
}
以上將 HEAD
定義完畢,接著我們看看 visit
inline fun <T : Tag> T.visit(crossinline block: T.() -> Unit) = visitTag { block() }
visitTag
就是之前看過的
inline fun <T : Tag> T.visitTag(block: T.() -> Unit) {
consumer.onTagStart(this)
this.block()
consumer.onTagEnd(this)
}
到這邊看完了 head
的內容,往下看 title
的實作
/**
* Document title
*/
@HtmlTagMarker
inline fun MetaDataContent.title(crossinline block : TITLE.() -> Unit = {}) : Unit = TITLE(emptyMap, consumer).visit(block)
TITLE
類別的實作則是
@Suppress("unused")
open class TITLE(initialAttributes : Map<String, String>, override val consumer : TagConsumer<*>) : HTMLTag("title", consumer, initialAttributes, null, false, false), HtmlHeadTag {
}
到這邊,我們應該發現了一個固定的模式。
這些函數最後所建立的內容,其實都是某個 HTMLTag
物件。建立了物件之後,由於他們都繼承了 HTMLTag
內所建立的函數,所以他們可以透過這些函數,產出對應的 HTML 內容。
使用這樣的語法,我們可以將產出內容的邏輯切換,改變成所使用類別的邏輯切換:你需要生成 head
內容就使用 HEAD
物件,需要生成 title
內容則使用 TITLE
物件,避免在控制時需要撰寫一個巨大的 switch-case 來生成內容。
另外,這種寫法利用 Kotlin 專屬的語法,如果要生成內容時,可以讓開發端用如此簡潔的寫法
val name = "Ktor"
head {
title {
+name
}
}
生成以下的內容
<head>
<title>Ktor</title>
</head>
今天我們就先看到這邊,明天我們一起來看
body {
h1 {
+"Hello from $name!"
}
}
這段背後的實作